package com.duojuhe.coremodule.quartz.util;

import com.duojuhe.coremodule.quartz.enums.QuartzEnum;
import com.duojuhe.common.enums.SystemEnum;
import com.duojuhe.common.exception.base.DuoJuHeException;
import com.duojuhe.common.result.ErrorCodes;
import com.duojuhe.common.utils.cronutil.CronExpressionUtil;
import com.duojuhe.common.utils.stringutil.StringUtil;
import com.duojuhe.coremodule.quartz.entity.QuartzJob;
import com.duojuhe.coremodule.quartz.util.sync.JobFactory;
import com.duojuhe.coremodule.quartz.util.sync.JobSyncFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;

import java.text.SimpleDateFormat;
import java.util.Date;

@Slf4j
public class ScheduleUtils {
    //job bean 标识key
    public final static String jobBeanKey = "jobBeanKey";

    /**
     * 定时任务白名单配置（仅允许访问的包名，如其他需要可以自行添加）
     */
    public static final String[] JOB_WHITELIST_STR = { "com.duojuhe.admin.spring.schedule.task" };


    /**
     * 获取触发器key
     *
     * @param jobId
     * @param jobGroupId
     * @return
     */
    public static TriggerKey getTriggerKey(String jobId, String jobGroupId) {
        return TriggerKey.triggerKey(jobId, jobGroupId);
    }

    /**
     * 获取表达式触发器
     *
     * @param scheduler  the scheduler
     * @param jobId      the job name
     * @param jobGroupId the job group
     * @return cron trigger
     * @throws SchedulerException
     */
    public static CronTrigger getCronTrigger(Scheduler scheduler, String jobId, String jobGroupId) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobId, jobGroupId);
        return (CronTrigger) scheduler.getTrigger(triggerKey);
    }

    /**
     * 创建任务
     *
     * @param scheduler the scheduler
     * @param quartzJob       the schedule job
     */
    public static void createScheduleJob(Scheduler scheduler, QuartzJob quartzJob) {
        try {
            if (quartzJob == null) {
                return;
            }
            if (!StringUtil.containsAnyIgnoreCase(quartzJob.getClassPath(), JOB_WHITELIST_STR)){
                log.error("任务执行路径不在白名单范围内，任务：{}", quartzJob);
                throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_CLASS_PATH_NO_WHITELIST);
            }
            if (!CronExpressionUtil.isValidExpression(quartzJob.getCronExpression())) {
                log.error("时间表达式错误，任务：{}", quartzJob);
                throw new DuoJuHeException(ErrorCodes.CRON_EXPRESSION_ERROR);
            }
            //作业任务id 唯一
            String jobId = quartzJob.getJobId();
            //作业分组id
            String jobGroupId = quartzJob.getGroupId();
            //是否同步 YES=是 NO=不是
            String isSync = quartzJob.getIsSyncCode();
            //时间表达式
            String cronExpression = quartzJob.getCronExpression();
            //同步或异步
            Class jobClass = SystemEnum.YES_NO.YES.getKey().equals(isSync) ? JobSyncFactory.class : JobFactory.class;
            //构建job信息
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobId, jobGroupId).build();
            //放入参数，运行时的方法可以获取
            jobDetail.getJobDataMap().put(jobBeanKey, quartzJob);
            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobId, jobGroupId).withSchedule(scheduleBuilder).build();
            // 判断是否存在
            JobKey jobKey = getJobKey(jobId, jobGroupId);
            if (scheduler.checkExists(jobKey)) {
                // 防止创建时存在数据问题 先移除，然后在执行创建操作
                scheduler.deleteJob(jobKey);
            }
            Date ft = scheduler.scheduleJob(jobDetail, trigger);
            // 暂停任务
            if (!QuartzEnum.QUARTZ_STATUS.RUNNING.getKey().equals(quartzJob.getJobStatusCode())) {
                scheduler.pauseJob(jobKey);
            }else {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                log.info(jobDetail.getKey() + " 已被安排执行于: " + sdf.format(ft) + "，并且以如下重复规则重复执行: " + trigger.getCronExpression());
            }
        } catch (Exception e) {
            log.error("创建定时任务出现异常",e);
            throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_CREATE_FAIL);
        }
    }





    /**
     * 更新定时任务,存在则更新，不存在则不更新
     *
     * @param scheduler      the scheduler
     * @param quartzJob            the param
     * @throws SchedulerException
     */
    public static void updateScheduleJob(Scheduler scheduler, QuartzJob quartzJob) {
        try {
            //作业任务id 唯一
            String jobId = quartzJob.getJobId();
            //作业分组id
            String jobGroupId = quartzJob.getGroupId();
            //时间表达式
            String cronExpression = quartzJob.getCronExpression();
            if (!CronExpressionUtil.isValidExpression(cronExpression)) {
                log.error("更新定时任务，时间表达式错误，任务：{}", quartzJob);
                throw new DuoJuHeException(ErrorCodes.CRON_EXPRESSION_ERROR);
            }
            if (!StringUtil.containsAnyIgnoreCase(quartzJob.getClassPath(), JOB_WHITELIST_STR)){
                log.error("任务执行路径不在白名单范围内，任务：{}", quartzJob);
                throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_CLASS_PATH_NO_WHITELIST);
            }
            JobKey jobKey = JobKey.jobKey(jobId, jobGroupId);
            if (!scheduler.checkExists(jobKey)) {
                log.error("更新定时任务，调度任务不存在队列中，则创建一个任务到队列中，任务：{}", quartzJob);
                createScheduleJob(scheduler, quartzJob);
                return;
            }
            TriggerKey triggerKey = getTriggerKey(jobId, jobGroupId);
            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            if (!QuartzEnum.QUARTZ_STATUS.RUNNING.getKey().equals(quartzJob.getJobStatusCode())) {
                scheduler.pauseJob(jobKey);
            }else {
                //按新的trigger重新设置job执行
                Date ft = scheduler.rescheduleJob(triggerKey, trigger);
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                log.info("更新安排执行于: " + sdf.format(ft) + "，并且以如下重复规则重复执行: " + trigger.getCronExpression());
            }
        } catch (Exception e) {
            log.error("更新定时任务出现异常",e);
            throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_UPDATE_FAIL);
        }
    }



    /**
     * 创建任务
     *
     * @param scheduler
     * @param quartzJob
     */
    public static void createSchedule(Scheduler scheduler, QuartzJob quartzJob) {
        try {
            if (!CronExpressionUtil.isValidExpression(quartzJob.getCronExpression())) {
                log.error("更新定时任务，时间表达式错误，任务：{}", quartzJob);
                throw new DuoJuHeException(ErrorCodes.CRON_EXPRESSION_ERROR);
            }
            //作业任务id 唯一
            String jobId = quartzJob.getJobId();
            //作业分组id
            String jobGroupId = quartzJob.getGroupId();
            // 不存在，创建一个
            JobKey jobKey = JobKey.jobKey(jobId, jobGroupId);
            if (!scheduler.checkExists(jobKey)) {
                log.info("要创建的调度作业不存在，执行新创建一个操作");
                createScheduleJob(scheduler, quartzJob);
            } else {
                log.info("要创建的调度作业存在，执行更新操作");
                // 已存在，那么更新相应的定时设置
                updateScheduleJobAndSchedule(scheduler, quartzJob);
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }





    /**
     * 更新定时任务并立即执行
     *
     * @param scheduler the scheduler
     * @param quartzJob       the schedule job
     * @throws SchedulerException
     */
    public static void updateScheduleJobAndSchedule(Scheduler scheduler, QuartzJob quartzJob) throws SchedulerException {
        //作业任务id 唯一
        String jobId = quartzJob.getJobId();
        //作业分组id
        String jobGroupId = quartzJob.getGroupId();
        updateScheduleJob(scheduler,quartzJob);
        resumeJob(scheduler, jobId, jobGroupId);
    }



    /**
     * 运行一次任务
     *
     * @param scheduler
     * @param jobId
     * @param jobGroupId
     */
    public static void runOnceJob(Scheduler scheduler, String jobId, String jobGroupId)  {
       try {
           JobKey jobKey = JobKey.jobKey(jobId, jobGroupId);
           if (!scheduler.checkExists(jobKey)) {
               throw new DuoJuHeException("任务不存在,执行失败!");
           }
           scheduler.triggerJob(jobKey);
       }catch (Exception e){
           log.error("运行一次任务出现异常",e);
           throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_RUN_FAIL);
       }
    }

    /**
     * 删除任务
     *
     * @param scheduler
     * @param jobId
     * @param jobGroupId
     */
    public static void deleteJob(Scheduler scheduler, String jobId, String jobGroupId){
        try {
            JobKey jobKey = JobKey.jobKey(jobId, jobGroupId);
            scheduler.deleteJob(jobKey);
        }catch (Exception e){
            log.error("删除任务出现异常",e);
            throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_DELETE_FAIL);
        }
    }


    /**
     * 暂停任务
     *
     * @param scheduler
     * @param jobId
     * @param jobGroupId
     */
    public static void pauseJob(Scheduler scheduler, String jobId, String jobGroupId) {
        try {
            JobKey jobKey = JobKey.jobKey(jobId, jobGroupId);
            scheduler.pauseJob(jobKey);
        }catch (Exception e){
            log.error("暂停任务出现异常",e);
            throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_PAUSE_FAIL);
        }
    }

    /**
     * 停止多个job任务
     *
     * @param groupMatcher
     * @throws SchedulerException
     */
    private static void pauseJobs(Scheduler scheduler, GroupMatcher<JobKey> groupMatcher) throws SchedulerException {
        scheduler.pauseJobs(groupMatcher);
    }



    /**
     * 按照分组停止分组下面的所有job任务
     *
     * @param jobGroupId
     * @throws SchedulerException
     */
    public static void pauseGroupJobs(Scheduler scheduler, String jobGroupId) throws SchedulerException {
        GroupMatcher<JobKey> groupMatcher = GroupMatcher.groupEquals(jobGroupId);
        pauseJobs(scheduler,groupMatcher);
    }


    /**
     * 恢复任务
     *
     * @param scheduler
     * @param jobId
     * @param jobGroupId
     * @throws SchedulerException
     */
    public static void resumeJob(Scheduler scheduler, String jobId, String jobGroupId) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobId, jobGroupId);
        scheduler.resumeJob(jobKey);
    }


    /**
     * 恢复任务或创建并执行任务
     *
     * @param scheduler
     * @param quartzJob
     */
    public static void resumeOrCreateJob(Scheduler scheduler, QuartzJob quartzJob) {
        try {
            //作业任务id 唯一
            String jobId = quartzJob.getJobId();
            //作业分组id
            String jobGroupId = quartzJob.getGroupId();
            if (StringUtils.isEmpty(quartzJob.getClassPath()) || StringUtils.isEmpty(quartzJob.getMethodName())) {
                return;
            }
            // 不存在，创建一个
            JobKey jobKey = JobKey.jobKey(jobId, jobGroupId);
            if (!scheduler.checkExists(jobKey)) {
                log.info("恢复任务不存在，创建一个");
                createScheduleJob(scheduler, quartzJob);
            } else {
                log.info("恢复任务存在，恢复执行");
                // 已存在，那么恢复执行
                scheduler.resumeJob(jobKey);
            }
        } catch (SchedulerException e) {
            log.error("恢复任务出现异常",e);
            throw new DuoJuHeException(ErrorCodes.QUARTZ_JOB_RESUME_FAIL);
        }
    }


    /**
     * 获取jobKey
     *
     * @param jobId      the job name
     * @param jobGroupId the job group
     * @return the job key
     */
    public static JobKey getJobKey(String jobId, String jobGroupId) {
        return JobKey.jobKey(jobId, jobGroupId);
    }





    /**
     * 删除定时任务
     *
     * @param scheduler
     * @param jobId
     * @param jobGroupId
     * @throws SchedulerException
     */
    public static void deleteScheduleJob(Scheduler scheduler, String jobId, String jobGroupId) throws SchedulerException {
        scheduler.deleteJob(getJobKey(jobId, jobGroupId));
    }
}
